Atraskite „JavaScript“ šablonų atitikimo galią. Sužinokite, kaip ši funkcinio programavimo koncepcija pranoksta „switch“ sakinius, užtikrindama švaresnį, deklaratyvesnį ir patikimesnį kodą.
Elegancijos galia: išsami „JavaScript“ šablonų atitikimo analizė
Dešimtmečius „JavaScript“ kūrėjai sąlyginei logikai naudojo gerai pažįstamus įrankius: seną gerą if/else grandinę ir klasikinį switch sakinį. Tai yra šakojimosi logikos darbiniai arkliai – funkcionalūs ir nuspėjami. Tačiau, mūsų programoms tampant sudėtingesnėms ir mums pritaikant tokias paradigmas kaip funkcinis programavimas, šių įrankių apribojimai tampa vis akivaizdesni. Ilgos if/else grandinės gali tapti sunkiai skaitomos, o switch sakiniai, su savo paprastais lygybės patikrinimais ir „fall-through“ keistenybėmis, dažnai nepakankami dirbant su sudėtingomis duomenų struktūromis.
Pristatome šablonų atitikimą. Tai ne tik „switch sakinys su steroidais“; tai paradigmos pokytis. Kilęs iš funkcinių kalbų, tokių kaip Haskell, ML ir Rust, šablonų atitikimas yra mechanizmas, skirtas reikšmės tikrinimui pagal šablonų seriją. Jis leidžia dekonstruoti sudėtingus duomenis, patikrinti jų formą ir vykdyti kodą, remiantis ta struktūra – viskas vienoje, išraiškingoje konstrukcijoje. Tai perėjimas nuo imperatyvaus tikrinimo („kaip patikrinti reikšmę“) prie deklaratyvaus atitikimo („kaip reikšmė atrodo“).
Šis straipsnis yra išsamus vadovas, kaip suprasti ir naudoti šablonų atitikimą „JavaScript“ kalboje jau šiandien. Išnagrinėsime jo pagrindines koncepcijas, praktinius pritaikymus ir kaip galite pasinaudoti bibliotekomis, kad įdiegtumėte šį galingą funkcinį modelį į savo projektus daug anksčiau, nei jis taps gimtąja kalbos funkcija.
Kas yra šablonų atitikimas? Žingsnis toliau už „switch“ sakinių
Iš esmės, šablonų atitikimas yra duomenų struktūrų dekonstravimo procesas, siekiant patikrinti, ar jos atitinka konkretų „šabloną“ ar formą. Jei randamas atitikimas, galime vykdyti susijusį kodo bloką, dažnai priskirdami atitinkančių duomenų dalis vietiniams kintamiesiems, kad galėtume juos naudoti tame bloke.
Palyginkime tai su tradiciniu switch sakiniu. switch yra apribotas griežtos lygybės (===) patikrinimais su viena reikšme:
function getHttpStatusMessage(status) {
switch (status) {
case 200:
return 'OK';
case 404:
return 'Not Found';
case 500:
return 'Internal Server Error';
default:
return 'Unknown Status';
}
}
Tai puikiai veikia su paprastomis, primityviomis reikšmėmis. Bet ką daryti, jei norėtume apdoroti sudėtingesnį objektą, pavyzdžiui, API atsakymą?
const response = { status: 'success', data: { user: 'John Doe' } };
// arba
const errorResponse = { status: 'error', error: { code: 'E401', message: 'Unauthorized' } };
switch sakinys negali elegantiškai su tuo susidoroti. Būtumėte priversti naudoti netvarkingą if/else sakinių seriją, tikrinančią savybių egzistavimą ir jų reikšmes. Štai kur šablonų atitikimas spindi. Jis gali patikrinti visą objekto formą.
Šablonų atitikimo metodas konceptualiai atrodytų taip (naudojant hipotetinę ateities sintaksę):
function handleResponse(response) {
return match (response) {
when { status: 'success', data: d }: `Sėkmė! Duomenys gauti vartotojui ${d.user}`,
when { status: 'error', error: e }: `Klaida ${e.code}: ${e.message}`,
default: 'Netinkamas atsakymo formatas'
}
}
Atkreipkite dėmesį į pagrindinius skirtumus:
- Struktūrinis atitikimas: Jis atitinka objekto formą, o ne tik vieną reikšmę.
- Duomenų susiejimas: Jis ištraukia įdėtas reikšmes (kaip `d` ir `e`) tiesiogiai šablone.
- Orientuotas į išraiškas: Visas `match` blokas yra išraiška, kuri grąžina reikšmę, pašalindama poreikį naudoti laikinus kintamuosius ir `return` sakinius kiekvienoje šakoje. Tai yra pagrindinis funkcinio programavimo principas.
Šablonų atitikimo padėtis „JavaScript“ kalboje
Svarbu aiškiai apibrėžti lūkesčius pasaulinei kūrėjų auditorijai: šablonų atitikimas dar nėra standartinė, gimtoji „JavaScript“ funkcija.
Yra aktyvus TC39 pasiūlymas įtraukti jį į ECMAScript standartą. Tačiau, rašant šį straipsnį, jis yra 1 etape, o tai reiškia, kad jis yra ankstyvojoje tyrimo fazėje. Tikėtina, praeis keleri metai, kol pamatysime jį natūraliai įdiegtą visose pagrindinėse naršyklėse ir Node.js aplinkose.
Tad, kaip galime jį naudoti šiandien? Galime pasikliauti gyvybinga „JavaScript“ ekosistema. Sukurta keletas puikių bibliotekų, kurios atneša šablonų atitikimo galią į šiuolaikinį „JavaScript“ ir „TypeScript“. Šiame straipsnyje pateikiamiems pavyzdžiams daugiausia naudosime ts-pattern – populiarią ir galingą biblioteką, kuri yra pilnai tipizuota, labai išraiškinga ir sklandžiai veikia tiek „TypeScript“, tiek paprastuose „JavaScript“ projektuose.
Pagrindinės funkcinio šablonų atitikimo koncepcijos
Pasinerkime į fundamentalius šablonus, su kuriais susidursite. Naudosime ts-pattern savo kodo pavyzdžiams, tačiau koncepcijos yra universalios daugumoje šablonų atitikimo implementacijų.
Literaliniai šablonai: paprasčiausias atitikimas
Tai pati pagrindinė atitikimo forma, panaši į `switch` `case`. Ji atitinka primityvias reikšmes, tokias kaip eilutės, skaičiai, loginės reikšmės, `null` ir `undefined`.
import { match } from 'ts-pattern';
function getPaymentMethod(method) {
return match(method)
.with('credit_card', () => 'Apdorojama per kredito kortelių sąsają')
.with('paypal', () => 'Nukreipiama į PayPal')
.with('crypto', () => 'Apdorojama per kriptovaliutų piniginę')
.otherwise(() => 'Netinkamas mokėjimo būdas');
}
console.log(getPaymentMethod('paypal')); // "Nukreipiama į PayPal"
console.log(getPaymentMethod('bank_transfer')); // "Netinkamas mokėjimo būdas"
.with(pattern, handler) sintaksė yra esminė. .otherwise() sąlyga yra `default` atvejo atitikmuo ir dažnai yra būtina, kad užtikrintų, jog atitikimas yra išsamus (apima visas galimybes).
Dekonstravimo šablonai: objektų ir masyvų išpakavimas
Štai kur šablonų atitikimas iš tiesų išsiskiria. Galite lyginti pagal objektų ir masyvų formą bei savybes.
Objektų dekonstravimas:
Įsivaizduokite, kad apdorojate įvykius programoje. Kiekvienas įvykis yra objektas su `type` ir `payload`.
import { match, P } from 'ts-pattern'; // P yra vietos rezervavimo objektas
function handleEvent(event) {
return match(event)
.with({ type: 'USER_LOGIN', payload: { userId: P.select() } }, (userId) => {
console.log(`Vartotojas ${userId} prisijungė.`);
// ... sukelia prisijungimo šalutinius efektus
})
.with({ type: 'ADD_TO_CART', payload: { productId: P.select('id'), quantity: P.select('qty') } }, ({ id, qty }) => {
console.log(`Į krepšelį pridėta ${qty} vnt. produkto ${id}.`);
})
.with({ type: 'PAGE_VIEW' }, () => {
console.log('Puslapio peržiūra užfiksuota.');
})
.otherwise(() => {
console.log('Gautas nežinomas įvykis.');
});
}
handleEvent({ type: 'USER_LOGIN', payload: { userId: 'u-123', timestamp: 1678886400 } });
handleEvent({ type: 'ADD_TO_CART', payload: { productId: 'prod-abc', quantity: 2 } });
Šiame pavyzdyje P.select() yra galingas įrankis. Jis veikia kaip pakaitos simbolis, kuris atitinka bet kokią reikšmę toje pozicijoje ir ją susieja, padarydamas ją prieinamą tvarkyklės funkcijai. Galite netgi pavadinti pasirinktas reikšmes, kad tvarkyklės signatūra būtų aprašomoji.
Masyvų dekonstravimas:
Taip pat galite lyginti pagal masyvų struktūrą, kas yra neįtikėtinai naudinga atliekant tokias užduotis kaip komandinės eilutės argumentų analizė ar darbas su rinkinio (tuple) tipo duomenimis.
function parseCommand(args) {
return match(args)
.with(['install', P.select()], (pkg) => `Diegiamas paketas: ${pkg}`)
.with(['delete', P.select(), '--force'], (file) => `Priverstinai trinamas failas: ${file}`)
.with(['list'], () => 'Išvardijami visi elementai...')
.with([], () => 'Nepateikta jokia komanda. Naudokite --help, kad gautumėte parinktis.')
.otherwise((unrecognized) => `Klaida: Neatpažinta komandų seka: ${unrecognized.join(' ')}`);
}
console.log(parseCommand(['install', 'react'])); // "Diegiamas paketas: react"
console.log(parseCommand(['delete', 'temp.log', '--force'])); // "Priverstinai trinamas failas: temp.log"
console.log(parseCommand([])); // "Nepateikta jokia komanda..."
Pakaitos simbolių ir vietos rezervavimo šablonai
Jau matėme P.select(), susiejantį vietos rezervavimo simbolį. ts-pattern taip pat siūlo paprastą pakaitos simbolį P._, kai reikia atitikti poziciją, bet jums nerūpi jos reikšmė.
P._(Pakaitos simbolis): Atitinka bet kokią reikšmę, bet jos nesusieja. Naudokite, kai reikšmė turi egzistuoti, bet jos nenaudosite.P.select()(Vietos rezervavimo simbolis): Atitinka bet kokią reikšmę ir susieja ją naudojimui tvarkyklėje.
match(data)
.with(['SUCCESS', P._, P.select()], (message) => `Sėkmė su pranešimu: ${message}`)
// Čia ignoruojame antrą elementą, bet užfiksuojame trečią.
.otherwise(() => 'Nėra sėkmės pranešimo');
Apsauginės sąlygos: sąlyginės logikos pridėjimas su .when()
Kartais vien formos atitikimo nepakanka. Gali prireikti pridėti papildomą sąlygą. Tam skirtos apsauginės sąlygos. ts-pattern bibliotekoje tai atliekama su .when() metodu arba P.when() predikatu.
Įsivaizduokite, kad apdorojate užsakymus. Norite skirtingai tvarkyti didelės vertės užsakymus.
function getOrderStatus(order) {
return match(order)
.with({ status: 'shipped', total: P.when(t => t > 1000) }, () => 'Didelės vertės užsakymas išsiųstas.')
.with({ status: 'shipped' }, () => 'Standartinis užsakymas išsiųstas.')
.with({ status: 'processing', items: P.when(items => items.length === 0) }, () => 'Įspėjimas: Apdorojamas tuščias užsakymas.')
.with({ status: 'processing' }, () => 'Užsakymas apdorojamas.')
.with({ status: 'cancelled' }, () => 'Užsakymas buvo atšauktas.')
.otherwise(() => 'Nežinoma užsakymo būsena.');
}
console.log(getOrderStatus({ status: 'shipped', total: 1500 })); // "Didelės vertės užsakymas išsiųstas."
console.log(getOrderStatus({ status: 'shipped', total: 50 })); // "Standartinis užsakymas išsiųstas."
console.log(getOrderStatus({ status: 'processing', items: [] })); // "Įspėjimas: Apdorojamas tuščias užsakymas."
Atkreipkite dėmesį, kad specifiškesnis šablonas (su .when() sąlyga) turi eiti prieš bendresnį. Laimi pirmas sėkmingai atitinkantis šablonas.
Tipų ir predikatų šablonai
Taip pat galite lyginti pagal duomenų tipus ar pasirinktines predikatų funkcijas, suteikdami dar daugiau lankstumo.
function describeValue(x) {
return match(x)
.with(P.string, () => 'Tai yra eilutė.')
.with(P.number, () => 'Tai yra skaičius.')
.with({ message: P.string }, () => 'Tai yra klaidos objektas.')
.with(P.instanceOf(Date), (d) => `Tai yra datos objektas, nurodantis ${d.getFullYear()} metus.` )
.otherwise(() => 'Tai yra kitokio tipo reikšmė.');
}
Praktiniai panaudojimo atvejai šiuolaikinėje žiniatinklio kūryboje
Teorija yra puiku, bet pažiūrėkime, kaip šablonų atitikimas sprendžia realias problemas pasaulinei kūrėjų auditorijai.
Sudėtingų API atsakymų tvarkymas
Tai klasikinis panaudojimo atvejis. API retai grąžina vieną, fiksuotą formą. Jie grąžina sėkmės objektus, įvairius klaidų objektus arba įkėlimo būsenas. Šablonų atitikimas tai puikiai sutvarko.
Klaida: Prašomas resursas nerastas. Įvyko netikėta klaida: ${err.message}// Tarkime, tai yra būsena iš duomenų paėmimo „kablio“ (hook)
const apiState = { status: 'error', error: { code: 403, message: 'Forbidden' } };
function renderUI(state) {
return match(state)
.with({ status: 'loading' }, () => '
.with({ status: 'success', data: P.select() }, (users) => `${users.map(u => `
`)
.with({ status: 'error', error: { code: 404 } }, () => '
.with({ status: 'error', error: P.select() }, (err) => `
.exhaustive(); // Užtikrina, kad visi mūsų būsenos tipo atvejai yra apdoroti
}
// document.body.innerHTML = renderUI(apiState);
Tai yra daug skaitomiau ir patikimiau nei įdėti if (state.status === 'success') patikrinimai.
Būsenos valdymas funkciniuose komponentuose (pvz., React)
Būsenos valdymo bibliotekose, tokiose kaip Redux, arba naudojant React `useReducer` „kablį“, dažnai turite reduktoriaus funkciją, kuri tvarko įvairius veiksmų tipus. `switch` pagal `action.type` yra įprastas, tačiau šablonų atitikimas pagal visą `action` objektą yra pranašesnis.
// Anksčiau: tipinis reduktorius su switch sakiniu
function classicReducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
case 'DECREMENT':
return { ...state, count: state.count - 1 };
case 'SET_VALUE':
return { ...state, count: action.payload };
default:
return state;
}
}
// Vėliau: reduktorius, naudojantis šablonų atitikimą
function patternMatchingReducer(state, action) {
return match(action)
.with({ type: 'INCREMENT' }, () => ({ ...state, count: state.count + 1 }))
.with({ type: 'DECREMENT' }, () => ({ ...state, count: state.count - 1 }))
.with({ type: 'SET_VALUE', payload: P.select() }, (value) => ({ ...state, count: value }))
.otherwise(() => state);
}
Šablonų atitikimo versija yra labiau deklaratyvi. Ji taip pat apsaugo nuo įprastų klaidų, pavyzdžiui, bandymo pasiekti `action.payload`, kai jis gali neegzistuoti tam tikram veiksmo tipui. Pats šablonas užtikrina, kad `payload` turi egzistuoti `'SET_VALUE'` atveju.
Baigtinių būsenų mašinų (FSM) įgyvendinimas
Baigtinė būsenų mašina yra skaičiavimo modelis, kuris gali būti vienoje iš baigtinio skaičiaus būsenų. Šablonų atitikimas yra puikus įrankis apibrėžti perėjimus tarp šių būsenų.
// Būsenos: { status: 'idle' } | { status: 'loading' } | { status: 'success', data: T } | { status: 'error', error: E }
// Įvykiai: { type: 'FETCH' } | { type: 'RESOLVE', data: T } | { type: 'REJECT', error: E }
function stateMachine(currentState, event) {
return match([currentState, event])
.with([{ status: 'idle' }, { type: 'FETCH' }], () => ({ status: 'loading' }))
.with([{ status: 'loading' }, { type: 'RESOLVE', data: P.select() }], (data) => ({ status: 'success', data }))
.with([{ status: 'loading' }, { type: 'REJECT', error: P.select() }], (error) => ({ status: 'error', error }))
.with([{ status: 'error' }, { type: 'FETCH' }], () => ({ status: 'loading' }))
.otherwise(() => currentState); // Visiems kitiems deriniams, likti dabartinėje būsenoje
}
Šis metodas padaro galiojančius būsenų perėjimus aiškius ir lengvai suprantamus.
Nauda kodo kokybei ir palaikomumui
Šablonų atitikimo pritaikymas nėra tik protingo kodo rašymas; jis turi apčiuopiamos naudos visam programinės įrangos kūrimo ciklui.
- Skaitomumas ir deklaratyvus stilius: Šablonų atitikimas verčia jus aprašyti, kaip atrodo jūsų duomenys, o ne imperatyvius žingsnius, kaip juos patikrinti. Tai daro jūsų kodo ketinimus aiškesnius kitiems kūrėjams, nepriklausomai nuo jų kultūrinės ar lingvistinės aplinkos.
- Nekintamumas ir grynosios funkcijos: Į išraiškas orientuotas šablonų atitikimo pobūdis puikiai dera su funkcinio programavimo principais. Jis skatina jus paimti duomenis, juos transformuoti ir grąžinti naują reikšmę, o ne tiesiogiai keisti būseną. Tai lemia mažiau šalutinių poveikių ir labiau nuspėjamą kodą.
- Išsamumo patikrinimas: Tai keičia žaidimo taisykles patikimumo srityje. Naudojant „TypeScript“, bibliotekos, tokios kaip `ts-pattern`, gali kompiliavimo metu užtikrinti, kad apdorojote kiekvieną įmanomą jungtinio tipo variantą. Jei pridėsite naują būsenos ar veiksmo tipą, kompiliatorius rodys klaidą, kol nepridėsite atitinkamo tvarkytojo savo `match` išraiškoje. Ši paprasta funkcija pašalina visą klasę vykdymo laiko klaidų.
- Sumažintas ciklometrinis sudėtingumas: Jis suplokština giliai įdėtas `if/else` struktūras į vieną, linijinį ir lengvai skaitomą bloką. Kodas su mažesniu sudėtingumu yra lengviau testuojamas, derinamas ir palaikomas.
Kaip pradėti naudoti šablonų atitikimą šiandien
Pasiruošę išbandyti? Štai paprastas, veiksmingas planas:
- Pasirinkite įrankį: Mes labai rekomenduojame
ts-patterndėl jo tvirto funkcijų rinkinio ir puikaus „TypeScript“ palaikymo. Tai yra auksinis standartas „JavaScript“ ekosistemoje šiandien. - Diegimas: Pridėkite jį prie savo projekto naudodami pasirinktą paketų tvarkytuvę.
npm install ts-pattern
arbayarn add ts-pattern - Refaktorizuokite nedidelę kodo dalį: Geriausias būdas mokytis – daryti. Raskite sudėtingą `switch` sakinį ar netvarkingą `if/else` grandinę savo kode. Tai gali būti komponentas, kuris atvaizduoja skirtingą vartotojo sąsają pagal savybes (props), funkcija, kuri analizuoja API duomenis, ar reduktorius. Pabandykite jį refaktorizuoti.
Pastaba dėl našumo
Dažnas klausimas yra, ar naudojant biblioteką šablonų atitikimui atsiranda našumo nuostolių. Atsakymas yra taip, bet tai beveik visada yra nereikšminga. Šios bibliotekos yra labai optimizuotos, o pridėtinės išlaidos yra minimalios didžiajai daliai žiniatinklio programų. Didžiulė nauda kūrėjų produktyvumui, kodo aiškumui ir klaidų prevencijai gerokai viršija mikrosekundžių lygio našumo kainą. Neoptimizuokite per anksti; teikite pirmenybę aiškaus, teisingo ir palaikomo kodo rašymui.
Ateitis: gimtasis šablonų atitikimas ECMAScript kalboje
Kaip minėta, TC39 komitetas dirba prie šablonų atitikimo pridėjimo kaip gimtosios funkcijos. Dėl sintaksės vis dar diskutuojama, bet ji galėtų atrodyti maždaug taip:
// Potenciali ateities sintaksė!
let httpMessage = match (response) {
when { status: 200, body: b } -> `Sėkmė su turiniu: ${b}`,
when { status: 404 } -> `Nerasta`,
when { status: 5.. } -> `Serverio klaida`,
else -> `Kitas HTTP atsakymas`
};
Mokydamiesi koncepcijų ir modelių šiandien su bibliotekomis, tokiomis kaip ts-pattern, jūs ne tik tobulinate savo dabartinius projektus; jūs ruošiatės „JavaScript“ kalbos ateičiai. Mentaliniai modeliai, kuriuos sukursite, tiesiogiai persikels, kai šios funkcijos taps gimtosios.
Išvada: paradigmos pokytis „JavaScript“ sąlyginei logikai
Šablonų atitikimas yra daug daugiau nei sintaksinis cukrus `switch` sakiniui. Tai reiškia fundamentalų poslinkį link deklaratyvesnio, patikimesnio ir funkcionalesnio sąlyginės logikos tvarkymo stiliaus „JavaScript“ kalboje. Jis skatina jus galvoti apie jūsų duomenų formą, o tai lemia kodą, kuris yra ne tik elegantiškesnis, bet ir atsparesnis klaidoms bei lengviau palaikomas laikui bėgant.
Kūrėjų komandoms visame pasaulyje šablonų atitikimo pritaikymas gali lemti nuoseklesnį ir išraiškingesnį kodą. Jis suteikia bendrą kalbą sudėtingų duomenų struktūrų tvarkymui, kuri peržengia paprastus mūsų tradicinių įrankių patikrinimus. Raginame jus išbandyti tai savo kitame projekte. Pradėkite nuo mažo, refaktorizuokite sudėtingą funkciją ir patirkite aiškumą bei galią, kurią tai suteikia jūsų kodui.